<!DOCTYPE html>


<html lang="en">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    dubbo服务自省 |  一曲秋殇
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/images/myfavicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-dubbo/dubbo服务自省"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  dubbo服务自省
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/dubbo/dubbo%E6%9C%8D%E5%8A%A1%E8%87%AA%E7%9C%81/" class="article-date">
  <time datetime="2021-12-13T05:51:40.040Z" itemprop="datePublished">2021-12-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/%E5%90%8E%E5%8F%B0/">后台</a> / <a class="article-category-link" href="/categories/%E5%90%8E%E5%8F%B0/java/">java</a> / <a class="article-category-link" href="/categories/%E5%90%8E%E5%8F%B0/java/dubbo/">dubbo</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> Word count:</span>
            <span class="post-count">4.6k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> Reading time≈</span>
            <span class="post-count">20 min</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><blockquote>
<p>Dubbo 从 2.7.0 版本开始增加了简化 URL的特性，通过开启simplified=true将服务URL中的一些数据存储到了元数据中心。但服务粒度依旧是服务接口级别，注册中心的URL数量并没有减少<br>在服务应用数量且服务应用中的服务接口众多的时候注册中心推送压力会很大，所以在Dubbo 2.7.5 版本引入了服务自省架构将注册到注册中心的粒度调整为应用级别，极大的减轻了注册中心的压力</p>
</blockquote>
<p>下面以zookeeper作为注册中心，元数据中心，配置中心。分别从服务提供端和消费端分析对服务自省的支持</p>
<h4 id="1-服务提供端"><a href="#1-服务提供端" class="headerlink" title="1.服务提供端"></a>1.服务提供端</h4><hr>
<h5 id="1-1-开启registry-type-service配置"><a href="#1-1-开启registry-type-service配置" class="headerlink" title="1.1 开启registry-type=service配置"></a>1.1 开启registry-type=service配置</h5><p>服务提供端要开启服务自省需要配置registry-type=service</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">config-center:</span><br><span class="line">  address: zookeeper://127.0.0.1:2181</span><br><span class="line">metadata-report:</span><br><span class="line">  address: zookeeper://127.0.0.1:2181</span><br><span class="line">registry:</span><br><span class="line">  address: zookeeper://127.0.0.1:2181</span><br><span class="line">  parameters:</span><br><span class="line">    registry-type: service</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意在使用zookeeper作为注册中心的时候检查下是不是导入了curator-x-discovery这个pom依赖，在写入实例数据到注册中心的时候会需要这个依赖，不然会报类未定义异常</p>
</blockquote>
<p>下面来逐步分析下这个配置带来的变化</p>
<h5 id="1-2-触发注册协议变更为service-discovery-registry"><a href="#1-2-触发注册协议变更为service-discovery-registry" class="headerlink" title="1.2 触发注册协议变更为service-discovery-registry"></a>1.2 触发注册协议变更为service-discovery-registry</h5><p>在<a href="/dubbo/springboot%E4%B8%ADdubbo%E6%9C%8D%E5%8A%A1%E5%AF%BC%E5%87%BA-1/#2-2-2-%E6%89%A7%E8%A1%8CServiceConfig-doExportUrls">springboot中dubbo服务导出-1#2.2.2</a>执行ServiceConfig#doExportUrls()方法的时候会通过ConfigValidationUtils.loadRegistries(this, true)去加载注册中心列表<br>该方法代码如下代码如下</p>
<figure class="highlight java"><figcaption><span>ConfigValidationUtils.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;URL&gt; <span class="title">loadRegistries</span><span class="params">(AbstractInterfaceConfig interfaceConfig, <span class="keyword">boolean</span> provider)</span> </span>&#123;</span><br><span class="line">    List&lt;URL&gt; registryList = <span class="keyword">new</span> ArrayList&lt;URL&gt;();</span><br><span class="line">    ApplicationConfig application = interfaceConfig.getApplication();</span><br><span class="line">    List&lt;RegistryConfig&gt; registries = interfaceConfig.getRegistries();</span><br><span class="line">    <span class="keyword">if</span> (CollectionUtils.isNotEmpty(registries)) &#123;</span><br><span class="line">        <span class="keyword">for</span> (RegistryConfig config : registries) &#123;</span><br><span class="line">            String address = config.getAddress();</span><br><span class="line">            <span class="keyword">if</span> (StringUtils.isEmpty(address)) &#123;</span><br><span class="line">                address = ANYHOST_VALUE;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) &#123;</span><br><span class="line">                Map&lt;String, String&gt; map = <span class="keyword">new</span> HashMap&lt;String, String&gt;();</span><br><span class="line">                AbstractConfig.appendParameters(map, application);</span><br><span class="line">                AbstractConfig.appendParameters(map, config);</span><br><span class="line">                map.put(PATH_KEY, RegistryService.class.getName());</span><br><span class="line">                AbstractInterfaceConfig.appendRuntimeParameters(map);</span><br><span class="line">                <span class="keyword">if</span> (!map.containsKey(PROTOCOL_KEY)) &#123;</span><br><span class="line">                    map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);</span><br><span class="line">                &#125;</span><br><span class="line">                List&lt;URL&gt; urls = UrlUtils.parseURLs(address, map);</span><br><span class="line">                <span class="keyword">for</span> (URL url : urls) &#123;</span><br><span class="line">                    url = URLBuilder.from(url)</span><br><span class="line">                            .addParameter(REGISTRY_KEY, url.getProtocol())</span><br><span class="line">                            <span class="comment">//这个地方调用extractRegistryType方法通过url去判断使用哪种协议注册协议</span></span><br><span class="line">                            <span class="comment">//如果url参数中有添加key为registry-type，value为service的参数，则就会使用service-discovery-registry协议，否则使用register协议</span></span><br><span class="line">                            .setProtocol(extractRegistryType(url))</span><br><span class="line">                            .build();</span><br><span class="line">                    <span class="keyword">if</span> ((provider &amp;&amp; url.getParameter(REGISTER_KEY, <span class="keyword">true</span>))</span><br><span class="line">                            || (!provider &amp;&amp; url.getParameter(SUBSCRIBE_KEY, <span class="keyword">true</span>))) &#123;</span><br><span class="line">                        registryList.add(url);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> registryList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从上面代码可知为注册中心配置添加registry-type: service时注册中心url的协议将会使用service-discovery-registry协议，这将会使得（<a href="/dubbo/springboot%E4%B8%ADdubbo%E6%9C%8D%E5%8A%A1%E5%AF%BC%E5%87%BA-1/#2-2-3-%E6%89%A7%E8%A1%8CServiceConfig-doExportUrlsFor1Protocol">springboot中dubbo服务导出-1#2.2.3</a>）执行<code>PROTOCOL.export(wrapperInvoker)</code>使用的拓展实现是 ServiceDiscoveryRegistryProtocol<br>这个协议拓展实现类的代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServiceDiscoveryRegistryProtocol</span> <span class="keyword">extends</span> <span class="title">RegistryProtocol</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> URL <span class="title">getRegistryUrl</span><span class="params">(Invoker&lt;?&gt; originInvoker)</span> </span>&#123;</span><br><span class="line">        URL registryUrl = originInvoker.getUrl();</span><br><span class="line">        <span class="keyword">if</span> (SERVICE_REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) &#123;</span><br><span class="line">            <span class="keyword">return</span> registryUrl;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">super</span>.getRegistryUrl(originInvoker);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> URL <span class="title">getRegistryUrl</span><span class="params">(URL url)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (SERVICE_REGISTRY_PROTOCOL.equals(url.getProtocol())) &#123;</span><br><span class="line">            <span class="keyword">return</span> url;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">super</span>.getRegistryUrl(url);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这个类继承自RegistryProtocol，重写了getRegistryUrl方法直接返回了registryUrl（在使用RegistryProtocol中getRegistryUrl时会将register协议更新为具体的注册协议比如zookeeper）<br>这将使得<a href="/dubbo/springboot%E4%B8%ADdubbo%E6%9C%8D%E5%8A%A1%E5%AF%BC%E5%87%BA-3/#1-%E6%89%A7%E8%A1%8CRegistryProtocol-getRegistry%E6%96%B9%E6%B3%95%E8%8E%B7%E5%8F%96Registry%E5%AF%B9%E8%B1%A1">springboot中dubbo服务导出-3#1</a>获取Register对象时使用的registryFactory为service-discovery-registry协议对应的 ServiceDiscoveryRegistryFactory<br>ServiceDiscoveryRegistryFactory重写了createRegistry方法，最终registryFactory.getRegistry(registryUrl)返回的对象为ServiceDiscoveryRegistry</p>
<p>ServiceDiscoveryRegistryFactory 代码如下：</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistryFactory.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServiceDiscoveryRegistryFactory</span> <span class="keyword">extends</span> <span class="title">AbstractRegistryFactory</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> Registry <span class="title">createRegistry</span><span class="params">(URL url)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (SERVICE_REGISTRY_PROTOCOL.equalsIgnoreCase(url.getProtocol())) &#123;</span><br><span class="line">            <span class="comment">//此处取出了url中registry参数指定的具体协议，并将其作为url的协议，比如指定的是zookeeper的话，url格式则为zookeeper://xxxx</span></span><br><span class="line">            <span class="comment">//因为将服务粒度调整为应用后终究还是要写入到对应的注册中心去的</span></span><br><span class="line">            String protocol = url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY);</span><br><span class="line">            url = url.setProtocol(protocol).removeParameter(REGISTRY_KEY);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//实例化一个ServiceDiscoveryRegistry对象进行返回</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ServiceDiscoveryRegistry(url);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接着继续看下 ServiceDiscoveryRegistry 的代码</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ServiceDiscoveryRegistry</span><span class="params">(URL registryURL)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//和其他Register实现一样都是继承自FailbackRegistry，先调用父类统一封装的逻辑</span></span><br><span class="line">    <span class="keyword">super</span>(registryURL);</span><br><span class="line">    <span class="comment">//这一步主要是通过registerURL协议类型去加载ServiceDiscovery拓展接口的具体实现（比如zookeeper协议的话对应的服务自身实现为ZookeeperServiceDiscovery），然后在通过EventPublishingServiceDiscovery类进行装饰</span></span><br><span class="line">    <span class="comment">//这一步在加载ZookeeperServiceDiscovery实例的时候会完成注册中心客户端初始化连接操作，供后面写入数据到注册中心</span></span><br><span class="line">    <span class="keyword">this</span>.serviceDiscovery = createServiceDiscovery(registryURL);</span><br><span class="line">    <span class="comment">//获取默认的服务名映射拓展实现，默认为CompositeServiceNameMapping</span></span><br><span class="line">    <span class="keyword">this</span>.serviceNameMapping = ServiceNameMapping.getDefaultExtension();</span><br><span class="line">    <span class="comment">//获取元数据存储类型，默认为local（注意这个地方是取得dubbo.metadata.storage-type的值，不是）</span></span><br><span class="line">    String metadataStorageType = getMetadataStorageType(registryURL);</span><br><span class="line">    <span class="comment">//通过上一步获取的元数据存储类型获取对应的写元数据的服务，local对应的是InMemoryWritableMetadataService，remote对应的是RemoteWritableMetadataService（RemoteWritableMetadataService写入操作委托给了InMemoryWritableMetadataService）</span></span><br><span class="line">    <span class="keyword">this</span>.writableMetadataService = WritableMetadataService.getExtension(metadataStorageType);</span><br><span class="line">    <span class="keyword">this</span>.subscribedURLsSynthesizers = initSubscribedURLsSynthesizers();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> ServiceDiscovery <span class="title">createServiceDiscovery</span><span class="params">(URL registryURL)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//通过url协议获取对应的ServiceDiscovery实现</span></span><br><span class="line">    ServiceDiscovery originalServiceDiscovery = getServiceDiscovery(registryURL);</span><br><span class="line">    <span class="comment">//通过EventPublishingServiceDiscovery类对原始serviceDiscovery进行包装</span></span><br><span class="line">    ServiceDiscovery serviceDiscovery = enhanceEventPublishing(originalServiceDiscovery);</span><br><span class="line">    execute(() -&gt; &#123;</span><br><span class="line">        serviceDiscovery.initialize(registryURL.addParameter(INTERFACE_KEY, ServiceDiscovery.class.getName())</span><br><span class="line">                .removeParameter(REGISTRY_TYPE_KEY));</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> serviceDiscovery;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>ServiceDiscoveryRegistry重写了doRegister，不会像ZookeeperRegistry#doRegister方法一样将暴露的接口服务URL写入到注册中心<br>而是将URL通过writableMetadataService写入到InMemoryWritableMetadataService#exportedServiceURLs的Map属性中，代码如下</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(URL url)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这里有个地方要注意的是只有provider侧才会执行下面的super.register(url)，最终调用doRegister方法</span></span><br><span class="line">    <span class="comment">//在开启了simplified=true的情况下url中找不到side参数是不会执行doRegister方法的</span></span><br><span class="line">    <span class="keyword">if</span> (!shouldRegister(url)) &#123; </span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">super</span>.register(url);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doRegister</span><span class="params">(URL url)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (writableMetadataService.exportURL(url)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (logger.isInfoEnabled()) &#123;</span><br><span class="line">            logger.info(format(<span class="string">&quot;The URL[%s] registered successfully.&quot;</span>, url.toString()));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (logger.isWarnEnabled()) &#123;</span><br><span class="line">            logger.info(format(<span class="string">&quot;The URL[%s] has been registered.&quot;</span>, url.toString()));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="1-3-将服务接口与应用的映射写入到配置中心"><a href="#1-3-将服务接口与应用的映射写入到配置中心" class="headerlink" title="1.3 将服务接口与应用的映射写入到配置中心"></a>1.3 将服务接口与应用的映射写入到配置中心</h5><p>在每个服务暴露完成后会执行ServiceConfig#exported方法进行派发ServiceConfigExportedEvent事件</p>
<figure class="highlight bash"><figcaption><span>ServiceConfig.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public void <span class="function"><span class="title">exported</span></span>() &#123;</span><br><span class="line">    // dispatch a ServiceConfigExportedEvent since 2.7.4</span><br><span class="line">    dispatch(new ServiceConfigExportedEvent(this));</span><br><span class="line">&#125;</span><br><span class="line">private void dispatch(Event event) &#123;</span><br><span class="line">    EventDispatcher.getDefaultExtension().dispatch(event);</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure>
<p>最终会通知到ServiceNameMappingListener这个监听器，该监听器代码如下：</p>
<figure class="highlight java"><figcaption><span>ServiceNameMappingListener.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//默认的ServiceNameMapping拓展为CompositeServiceNameMapping</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ServiceNameMapping serviceNameMapping = getDefaultExtension();</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEvent</span><span class="params">(ServiceConfigExportedEvent event)</span> </span>&#123;</span><br><span class="line">    ServiceConfig serviceConfig = event.getServiceConfig();</span><br><span class="line">    List&lt;URL&gt; exportedURLs = serviceConfig.getExportedUrls();</span><br><span class="line">    exportedURLs.forEach(url -&gt; &#123;</span><br><span class="line">        <span class="comment">//将暴露的URL传入CompositeServiceNameMapping#map进行调用</span></span><br><span class="line">        serviceNameMapping.map(url);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>CompositeServiceNameMapping#map代码如下：</p>
<figure class="highlight java"><figcaption><span>CompositeServiceNameMapping.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">map</span><span class="params">(URL exportedURL)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这个getServiceNameMappings方法中将会去获取所有ServiceNameMapping拓展实现然后移除本身的CompositeServiceNameMapping实现，最终会剩下下面三个拓展实现</span></span><br><span class="line">    <span class="comment">//ParameterizedServiceNameMapping,PropertiesFileServiceNameMapping,DynamicConfigurationServiceNameMapping</span></span><br><span class="line">    <span class="comment">//其中DynamicConfigurationServiceNameMapping这个拓展实现的map方法会将服务接口名称和应用名称映射关系写入到配置中心</span></span><br><span class="line">    List&lt;ServiceNameMapping&gt; serviceNameMappings = getServiceNameMappings();</span><br><span class="line">    serviceNameMappings.forEach(serviceNameMapping -&gt; serviceNameMapping.map(exportedURL));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最终写入的数据长下面那个样子（c3-provider-2为应用名称）<br><img src="http://img.luobiao.vip/img/20210420195057.png"></p>
<h5 id="1-4-执行暴露元数据服务操作"><a href="#1-4-执行暴露元数据服务操作" class="headerlink" title="1.4 执行暴露元数据服务操作"></a>1.4 执行暴露元数据服务操作</h5><p>在应用中所有的服务都暴露完成后，也就是DubboBootstrap#start方法中的<code>exportServices();</code>执行完毕，接着执行元数据服务暴露及注册服务实例的操作了<br>这里先贴出start方法代码</p>
<figure class="highlight java"><figcaption><span>DubboBootstrap.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> DubboBootstrap <span class="title">start</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (started.compareAndSet(<span class="keyword">false</span>, <span class="keyword">true</span>)) &#123;</span><br><span class="line">        ready.set(<span class="keyword">false</span>);</span><br><span class="line">        initialize();</span><br><span class="line">        <span class="comment">//所有接口服务暴露完毕</span></span><br><span class="line">        exportServices();</span><br><span class="line">        <span class="comment">//这个地方的hasExportedServices方法回去判断InMemoryWritableMetadataService#exportedServiceURLs中是不是有暴露完成的接口服务的URL</span></span><br><span class="line">        <span class="comment">//在上面的ServiceDiscoveryRegistry#doRegister方法中每个服务接口暴露完成都会写入到exportedServiceURLs这个map中，所有没开启registry-type: service的情况下是不会满足hasExportedServices()这个条件的</span></span><br><span class="line">        <span class="keyword">if</span> (!isOnlyRegisterProvider() || hasExportedServices()) &#123;</span><br><span class="line">            <span class="comment">//暴露元数据服务</span></span><br><span class="line">            exportMetadataService();</span><br><span class="line">            <span class="comment">//注册服务实例</span></span><br><span class="line">            registerServiceInstance();</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="comment">//省略后面的代码....</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>exportMetadataService代码如下：</p>
<figure class="highlight java"><figcaption><span>DubboBootstrap.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">exportMetadataService</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这个metadataServiceExporters在DubboBootstrap#initialize方法中赋值好了，为ConfigurableMetadataServiceExporter，RemoteMetadataServiceExporter这两个值</span></span><br><span class="line">    metadataServiceExporters</span><br><span class="line">            .stream()</span><br><span class="line">            <span class="comment">//过来掉不支持配置的metadata-type值的MetadataServiceExporter，从构造方法中可以看出ConfigurableMetadataServiceExporter支持local,composite,remote这三种，而RemoteMetadataServiceExporter不支持local</span></span><br><span class="line">            .filter(<span class="keyword">this</span>::supports)</span><br><span class="line">            <span class="comment">//过来完后对符合要求的MetadataServiceExporter执行export方法进行暴露元数据服务</span></span><br><span class="line">            .forEach(MetadataServiceExporter::export);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最终执行暴露的是通过doExport这个模板方法，先来看看ConfigurableMetadataServiceExporter这个类对该方法的实现，因为即使配置的metadata-type为remote也是会执行这个方法进行暴露元数据服务的</p>
<figure class="highlight java"><figcaption><span>ConfigurableMetadataServiceExporter.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doExport</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    ServiceConfig&lt;MetadataService&gt; serviceConfig = <span class="keyword">new</span> ServiceConfig&lt;&gt;();</span><br><span class="line">    serviceConfig.setApplication(getApplicationConfig());</span><br><span class="line">    serviceConfig.setRegistries(getRegistries());</span><br><span class="line">    <span class="comment">//generateMetadataProtocol这个方法中设置的端口为-1，表示从默认端口20880开始查找没有被占用的端口</span></span><br><span class="line">    <span class="comment">//所以当服务的端口设置为20880，再去启动另外一个20881的服务时经常会出现端口被占用，因为20881已经被前面服务作为元数据服务端口使用了</span></span><br><span class="line">    serviceConfig.setProtocol(generateMetadataProtocol());</span><br><span class="line">    serviceConfig.setInterface(MetadataService.class);</span><br><span class="line">    serviceConfig.setRef(metadataService);</span><br><span class="line">    serviceConfig.setGroup(getApplicationConfig().getName());</span><br><span class="line">    serviceConfig.setVersion(metadataService.version());</span><br><span class="line">    <span class="comment">//像其他业务服务一样进行暴露</span></span><br><span class="line">    serviceConfig.export();</span><br><span class="line">    <span class="keyword">this</span>.serviceConfig = serviceConfig;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接着再来看看RemoteMetadataServiceExporter的doExport实现</p>
<figure class="highlight java"><figcaption><span>RemoteMetadataServiceExporter.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doExport</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    <span class="comment">//获取默认的WritableMetadataService，也就是InMemoryWritableMetadataService，因为暴露的URL最终都是写到了InMemoryWritableMetadataService#exportedServiceURLs中</span></span><br><span class="line">    WritableMetadataService metadataServiceDelegate = WritableMetadataService.getDefaultExtension();</span><br><span class="line">    <span class="comment">//先执行publishServiceMetadata方法发布这些服务URL到远程元数据中心，默认会在/dubbo/exported-urls/目录下</span></span><br><span class="line">    <span class="keyword">if</span> (publishServiceMetadata(metadataServiceDelegate)) &#123;</span><br><span class="line">        <span class="comment">//如果发布成功则会通知那些订阅了该服务的消费者</span></span><br><span class="line">        publicConsumerMetadata(metadataServiceDelegate);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">publishServiceMetadata</span><span class="params">(WritableMetadataService metadataServiceDelegate)</span> </span>&#123;</span><br><span class="line">    String serviceName = metadataServiceDelegate.serviceName();</span><br><span class="line">    SortedSet&lt;String&gt; exportedURLs = metadataServiceDelegate.getExportedURLs();</span><br><span class="line">    String revision = urlRevisionResolver.resolve(exportedURLs);</span><br><span class="line">    <span class="keyword">return</span> getMetadataReport().saveExportedURLs(serviceName, revision, exportedURLs);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">publicConsumerMetadata</span><span class="params">(WritableMetadataService metadataServiceDelegate)</span> </span>&#123;</span><br><span class="line">    String serviceName = metadataServiceDelegate.serviceName();</span><br><span class="line">    SortedSet&lt;String&gt; subscribedURLs = metadataServiceDelegate.getSubscribedURLs();</span><br><span class="line">    String revision = urlRevisionResolver.resolve(subscribedURLs);</span><br><span class="line">    getMetadataReport().saveSubscribedData(<span class="keyword">new</span> SubscriberMetadataIdentifier(serviceName, revision), subscribedURLs);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure>
<h5 id="1-5-注册服务实例写入到注册中心操作"><a href="#1-5-注册服务实例写入到注册中心操作" class="headerlink" title="1.5 注册服务实例写入到注册中心操作"></a>1.5 注册服务实例写入到注册中心操作</h5><p>registerServiceInstance方法代码如下：</p>
<figure class="highlight java"><figcaption><span>DubboBootstrap.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerServiceInstance</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//已创建的注册中心连接中没有service-discovery-registry协议的就直接return了</span></span><br><span class="line">    <span class="keyword">if</span> (CollectionUtils.isEmpty(getServiceDiscoveries())) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ApplicationConfig application = getApplication();</span><br><span class="line">    String serviceName = application.getName();</span><br><span class="line">    <span class="comment">//如果暴露的URl中没有rest协议的，就随机返回一个，有的话就优先使用rest协议的URL</span></span><br><span class="line">    URL exportedURL = selectMetadataServiceExportedURL();</span><br><span class="line">    String host = exportedURL.getHost();</span><br><span class="line">    <span class="keyword">int</span> port = exportedURL.getPort();</span><br><span class="line">    <span class="comment">//创建服务实例对象</span></span><br><span class="line">    ServiceInstance serviceInstance = createServiceInstance(serviceName, host, port);</span><br><span class="line">    <span class="comment">//通过ServiceInstanceCustomizer类型的拓展对serviceInstance实例进行定制化</span></span><br><span class="line">    preRegisterServiceInstance(serviceInstance);</span><br><span class="line">    <span class="comment">//对符合要求的serviceDiscovery循序执行注册操作，serviceDiscovery会被EventPublishingServiceDiscovery所装饰，所以最终执行EventPublishingServiceDiscovery#register</span></span><br><span class="line">    getServiceDiscoveries().forEach(serviceDiscovery -&gt; serviceDiscovery.register(serviceInstance));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>EventPublishingServiceDiscovery#register方法代码如下</p>
<figure class="highlight java"><figcaption><span>EventPublishingServiceDiscovery.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(ServiceInstance serviceInstance)</span> <span class="keyword">throws</span> RuntimeException </span>&#123;</span><br><span class="line">    assertDestroyed(REGISTER_ACTION);</span><br><span class="line">    assertInitialized(REGISTER_ACTION);</span><br><span class="line">    executeWithEvents(</span><br><span class="line">            of(<span class="keyword">new</span> ServiceInstancePreRegisteredEvent(serviceDiscovery, serviceInstance)),</span><br><span class="line">            <span class="comment">//这一步就会执行注册实例到注册中心了</span></span><br><span class="line">            () -&gt; serviceDiscovery.register(serviceInstance),</span><br><span class="line">            of(<span class="keyword">new</span> ServiceInstanceRegisteredEvent(serviceDiscovery, serviceInstance))</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最终写入到注册中心的数据如下图所示：</p>
<p><img src="http://img.luobiao.vip/img/20210421094651.png"></p>
<p>其中c3-provider-2为dubbo服务的应用名称，192.168.10.120 为应用启动的主机，20880 为应用启动的端口</p>
<p>而192.168.10.120.20880节点的值为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">	<span class="string">&quot;name&quot;</span>: <span class="string">&quot;c3-provider-2&quot;</span>,</span><br><span class="line">	<span class="string">&quot;id&quot;</span>: <span class="string">&quot;192.168.10.120:20880&quot;</span>,</span><br><span class="line">	<span class="string">&quot;address&quot;</span>: <span class="string">&quot;192.168.10.120&quot;</span>,</span><br><span class="line">	<span class="string">&quot;port&quot;</span>: 20880,</span><br><span class="line">	<span class="string">&quot;sslPort&quot;</span>: null,</span><br><span class="line">	<span class="string">&quot;payload&quot;</span>: &#123;</span><br><span class="line">		<span class="string">&quot;@class&quot;</span>: <span class="string">&quot;org.apache.dubbo.registry.zookeeper.ZookeeperInstance&quot;</span>,</span><br><span class="line">		<span class="string">&quot;id&quot;</span>: null,</span><br><span class="line">		<span class="string">&quot;name&quot;</span>: <span class="string">&quot;c3-provider-2&quot;</span>,</span><br><span class="line">		<span class="string">&quot;metadata&quot;</span>: &#123;</span><br><span class="line">			<span class="string">&quot;dubbo.metadata-service.url-params&quot;</span>: <span class="string">&quot;&#123;\&quot;dubbo\&quot;:&#123;\&quot;version\&quot;:\&quot;1.0.0\&quot;,\&quot;dubbo\&quot;:\&quot;2.0.2\&quot;,\&quot;release\&quot;:\&quot;2.7.8\&quot;,\&quot;port\&quot;:\&quot;20881\&quot;&#125;&#125;&quot;</span>,</span><br><span class="line">			<span class="string">&quot;dubbo.subscribed-services.revision&quot;</span>: <span class="string">&quot;X&quot;</span>,</span><br><span class="line">			<span class="string">&quot;dubbo.endpoints&quot;</span>: <span class="string">&quot;[&#123;\&quot;port\&quot;:20880,\&quot;protocol\&quot;:\&quot;dubbo\&quot;&#125;]&quot;</span>,</span><br><span class="line">			<span class="string">&quot;dubbo.metadata.storage-type&quot;</span>: <span class="string">&quot;remote&quot;</span>,</span><br><span class="line">			<span class="string">&quot;dubbo.exported-services.revision&quot;</span>: <span class="string">&quot;9e7d487217a6fe01&quot;</span></span><br><span class="line">		&#125;</span><br><span class="line">	&#125;,</span><br><span class="line">	<span class="string">&quot;registrationTimeUTC&quot;</span>: 1618913497670,</span><br><span class="line">	<span class="string">&quot;serviceType&quot;</span>: <span class="string">&quot;DYNAMIC&quot;</span>,</span><br><span class="line">	<span class="string">&quot;uriSpec&quot;</span>: null</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="2-消费端"><a href="#2-消费端" class="headerlink" title="2.消费端"></a>2.消费端</h4><hr>
<h5 id="2-1-开启registry-type-service配置"><a href="#2-1-开启registry-type-service配置" class="headerlink" title="2.1 开启registry-type=service配置"></a>2.1 开启registry-type=service配置</h5><p>服务端开启了服务自身，注册到注册中心的服务粒度为应用，所以消费端也需要做对应的配置，不然会找不到服务</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">config-center:</span><br><span class="line">  address: zookeeper://127.0.0.1:2181</span><br><span class="line">metadata-report:</span><br><span class="line">  address: zookeeper://127.0.0.1:2181</span><br><span class="line">registry:</span><br><span class="line">  address: zookeeper://127.0.0.1:2181</span><br><span class="line">  parameters:</span><br><span class="line">    registry-type: service</span><br></pre></td></tr></table></figure>
<p>经过上面的配置后，和服务提供者端一样，在通过registryFactory.getRegistry(registryUrl)获取Registry对象的时候得到的是ServiceDiscoveryRegistry<br>只不过这一步是发生在RegistryProtocol#refer方法，具体可查看<a href="/dubbo/springboot%E4%B8%ADdubbo%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8/#2-1-1-%E9%80%9A%E8%BF%87Protocol-refer%E6%96%B9%E6%B3%95%E8%8E%B7%E5%8F%96Invoker%E5%AF%B9%E8%B1%A1">springboot中dubbo服务引用/#2.1.1</a></p>
<p>这将使得对提供者添加订阅（可查看<a href="/dubbo/springboot%E4%B8%ADdubbo%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8/#2-1-1-3-%E8%AE%A2%E9%98%85%E5%AF%B9%E5%BA%94%E6%9C%8D%E5%8A%A1%E6%8F%90%E4%BE%9B%E8%80%85%E5%8F%8A%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E9%85%8D%E7%BD%AE%E5%8F%98%E6%9B%B4">springboot中dubbo服务引用/#2.1.1.3</a>）的时候会调用ServiceDiscoveryRegistry#subscribe方法<br>ServiceDiscoveryRegistry#subscribe方法代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(URL url, NotifyListener listener)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//不是消费端的话不执行订阅操作</span></span><br><span class="line">    <span class="keyword">if</span> (!shouldSubscribe(url)) &#123; </span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//执行订阅操作，最终父类又会调子类的doSubscribe方法进行执行订阅</span></span><br><span class="line">    <span class="keyword">super</span>.subscribe(url, listener);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>ServiceDiscoveryRegistry#doSubscribe方法的代码如下：</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">subscribeURLs</span><span class="params">(URL url, NotifyListener listener)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//将需要订阅的URL添加到InMemoryWritableMetadataService#subscribedServiceURLs Map中</span></span><br><span class="line">    writableMetadataService.subscribeURL(url);</span><br><span class="line">    <span class="comment">//通过ServiceNameMapping拓展获取提供引用服务接口的应用的名称列表，详见2.2</span></span><br><span class="line">    Set&lt;String&gt; serviceNames = getServices(url);</span><br><span class="line">    List&lt;URL&gt; subscribedURLs = <span class="keyword">new</span> LinkedList&lt;&gt;();</span><br><span class="line">    <span class="comment">//对每个应用名称执行获取实例及添加订阅操作，详见2.3</span></span><br><span class="line">    serviceNames.forEach(serviceName -&gt; &#123;</span><br><span class="line">        <span class="comment">//通过应用名称去注册中心查找实例，然后通过实例信息建立元数据服务连接获取服务提供端那些暴露的URL添加到subscribedURLs中</span></span><br><span class="line">        subscribeURLs(url, subscribedURLs, serviceName);</span><br><span class="line">        <span class="comment">//添加该引用名称服务实例变更监听</span></span><br><span class="line">        registerServiceInstancesChangedListener(url, <span class="keyword">new</span> ServiceInstancesChangedListener(serviceName) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEvent</span><span class="params">(ServiceInstancesChangedEvent event)</span> </span>&#123;</span><br><span class="line">                List&lt;URL&gt; subscribedURLs = <span class="keyword">new</span> LinkedList&lt;&gt;();</span><br><span class="line">                Set&lt;String&gt; others = <span class="keyword">new</span> HashSet&lt;&gt;(serviceNames);</span><br><span class="line">                others.remove(serviceName);</span><br><span class="line">                subscribeURLs(url, subscribedURLs, serviceName, () -&gt; event.getServiceInstances());</span><br><span class="line">                subscribeURLs(url, subscribedURLs, others.toString(), () -&gt; getServiceInstances(others));</span><br><span class="line">                notifyAllSubscribedURLs(url, subscribedURLs, listener);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="comment">//详情见 2.4</span></span><br><span class="line">    notifyAllSubscribedURLs(url, subscribedURLs, listener);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="2-2-通过ServiceNameMapping获取应用名称列表"><a href="#2-2-通过ServiceNameMapping获取应用名称列表" class="headerlink" title="2.2 通过ServiceNameMapping获取应用名称列表"></a>2.2 通过ServiceNameMapping获取应用名称列表</h5><p>ServiceDiscoveryRegistry#getServices的代码如下</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> Set&lt;String&gt; <span class="title">getServices</span><span class="params">(URL subscribedURL)</span> <span class="keyword">throws</span> IllegalStateException </span>&#123;</span><br><span class="line">    Set&lt;String&gt; subscribedServices = <span class="keyword">null</span>;</span><br><span class="line">    <span class="comment">//取出provided-by参数值，如果有指定的话就直接使用指定的名称作为应用名称</span></span><br><span class="line">    String serviceNames = subscribedURL.getParameter(PROVIDED_BY);</span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNotEmpty(serviceNames)) &#123;</span><br><span class="line">        subscribedServices = parseServices(serviceNames);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (isEmpty(subscribedServices)) &#123;</span><br><span class="line">        <span class="comment">//没有指定的的话会进入这里，通过findMappedServices方法进行获取，方法代码见下面</span></span><br><span class="line">        subscribedServices = findMappedServices(subscribedURL);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (isEmpty(subscribedServices)) &#123;</span><br><span class="line">        subscribedServices = getSubscribedServices();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (isEmpty(subscribedServices)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;Should has at least one way to know which services this interface belongs to, subscription url: &quot;</span> + subscribedURL);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> subscribedServices;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>findMappedServices方法代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;String&gt; <span class="title">get</span><span class="params">(URL subscribedURL)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//getServiceNameMappings会获取到下面三个ServiceNameMapping拓展</span></span><br><span class="line">    <span class="comment">//ParameterizedServiceNameMapping,PropertiesFileServiceNameMapping,DynamicConfigurationServiceNameMapping</span></span><br><span class="line">    List&lt;ServiceNameMapping&gt; serviceNameMappings = getServiceNameMappings();</span><br><span class="line">    Set&lt;String&gt; serviceNames = <span class="keyword">null</span>;</span><br><span class="line">    <span class="comment">//循序上面获取的三个ServiceNameMapping进行获取应用名称，如果其中一个有获取到则直接跳出循序，所有应用名称的获取是有优先级的，DynamicConfigurationServiceNameMapping优先级最低，是从配置中心获取</span></span><br><span class="line">    <span class="keyword">for</span> (ServiceNameMapping serviceNameMapping : serviceNameMappings) &#123;</span><br><span class="line">        serviceNames = serviceNameMapping.get(subscribedURL);</span><br><span class="line">        <span class="keyword">if</span> (isNotEmpty(serviceNames)) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> serviceNames == <span class="keyword">null</span> ? emptySet() : unmodifiableSet(serviceNames);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="2-3-对每个应用名称执行获取实例及添加订阅实例监听操作"><a href="#2-3-对每个应用名称执行获取实例及添加订阅实例监听操作" class="headerlink" title="2.3 对每个应用名称执行获取实例及添加订阅实例监听操作"></a>2.3 对每个应用名称执行获取实例及添加订阅实例监听操作</h5><p>在上一步通过ServiceNameMapping获取到服务接口对应的应用名称列表后，循环应用名称做获取实例和订阅实例监听的操作</p>
<p>获取实例列表的方法如下：</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">subscribeURLs</span><span class="params">(URL url, List&lt;URL&gt; subscribedURLs, String serviceName)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//通过应用名称去注册中心获取实例列表</span></span><br><span class="line">    List&lt;ServiceInstance&gt; serviceInstances = serviceDiscovery.getInstances(serviceName);</span><br><span class="line">    <span class="comment">//这一步主要是通过实例列表获取服务提供者中已经暴露的符合要求的URL添加到subscribedURLs中，下面详细分析</span></span><br><span class="line">    subscribeURLs(url, subscribedURLs, serviceName, serviceInstances);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>subscribeURLs代码如下</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">subscribeURLs</span><span class="params">(URL subscribedURL, List&lt;URL&gt; subscribedURLs, String serviceName,Collection&lt;ServiceInstance&gt; serviceInstances)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (isEmpty(serviceInstances)) &#123;</span><br><span class="line">        logger.warn(format(<span class="string">&quot;There is no instance in service[name : %s]&quot;</span>, serviceName));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//通过getExportedURLs方法获取暴露的URL添加到subscribedURLs中</span></span><br><span class="line">    subscribedURLs.addAll(getExportedURLs(subscribedURL, serviceInstances));</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (subscribedURLs.isEmpty()) &#123;</span><br><span class="line">        subscribedURLs.addAll(synthesizeSubscribedURLs(subscribedURL, serviceInstances));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>getExportedURLs方法代码如下</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> List&lt;URL&gt; <span class="title">getExportedURLs</span><span class="params">(URL subscribedURL, Collection&lt;ServiceInstance&gt; instances)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//过滤掉那些不符合要求的实例 </span></span><br><span class="line">    List&lt;ServiceInstance&gt; serviceInstances = instances.stream()</span><br><span class="line">            .filter(ServiceInstance::isEnabled)</span><br><span class="line">            .filter(ServiceInstance::isHealthy)</span><br><span class="line">            .filter(ServiceInstanceMetadataUtils::isDubboServiceInstance)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    <span class="keyword">int</span> size = serviceInstances.size();</span><br><span class="line">    <span class="keyword">if</span> (size == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> emptyList();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//这一步的大概逻辑为</span></span><br><span class="line">    <span class="comment">//1.通过自适应拓展ServiceInstanceSelector#select方法从实例列表中随机获取获取一个实例（默认的拓展实现为RandomServiceInstanceSelector）</span></span><br><span class="line">    <span class="comment">//2.从1中获取的实例信息中取出元数据服务连接信息，通过连接元数据服务获取该实例下的暴露的所有接口服务URL</span></span><br><span class="line">    <span class="comment">//3.将2中返回的URL放入一个Map&lt;String, Map&lt;String, List&lt;URL&gt;&gt;&gt; serviceRevisionExportedURLsCache属性中，key为应用名称，值Map中的key为实例的revision值，值Map中的value为该实例下暴露的所有接口服务URL</span></span><br><span class="line">    <span class="comment">//revision是实例下暴露的所有接口服务URL计算的一个hash值，所有相同revision值的实例就没有必要去通过元数据服务去获取了</span></span><br><span class="line">    prepareServiceRevisionExportedURLs(serviceInstances);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//这一步主要是循序实例列表 通过实例中的应用名称+revision从serviceRevisionExportedURLsCache中取出URL,然后过滤出本次订阅的服务接口放入subscribedURLs中</span></span><br><span class="line">    <span class="comment">//注意，虽然只有一台实例通过元数据服务获取过暴露的URL，但如果其他实例的revision值和去获取URL实例的revision值一样的话也是会添加到subscribedURLs中的(URL中的host或port会替换为其他实例对应的host和port)</span></span><br><span class="line">    List&lt;URL&gt; subscribedURLs = cloneExportedURLs(subscribedURL, serviceInstances);</span><br><span class="line"></span><br><span class="line">    serviceInstances.clear();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> subscribedURLs;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="2-4-更新服务目录中的Invoke列表"><a href="#2-4-更新服务目录中的Invoke列表" class="headerlink" title="2.4 更新服务目录中的Invoke列表"></a>2.4 更新服务目录中的Invoke列表</h5><p>notifyAllSubscribedURLs方法代码如下</p>
<figure class="highlight java"><figcaption><span>ServiceDiscoveryRegistry.class</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">notifyAllSubscribedURLs</span><span class="params">(URL url, List&lt;URL&gt; subscribedURLs, NotifyListener listener)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (subscribedURLs.isEmpty()) &#123;</span><br><span class="line">        subscribedURLs.add(from(url).setProtocol(EMPTY_PROTOCOL).removeParameter(CATEGORY_KEY).build());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//这个地方这个listener就是RegistryDirectory对象，该对象实现了NotifyListener接口，在RegistryDirectory#notify中实现了更新Invoke列表的逻辑</span></span><br><span class="line">    listener.notify(subscribedURLs);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>上面服务提供端和消费端的总体流程可总结为下面这个图</p>
<p><img src="http://img.luobiao.vip/img/20210422173237.png"></p>
<p><a target="_blank" rel="noopener" href="https://www.processon.com/view/link/60814343e401fd53c7b69ba7">原图链接</a></p>
<ol>
<li>provider应用启动后循环暴露接口服务</li>
<li>在每个接口服务暴露后会将应用名称和接口的映射关系写入到配置中心</li>
<li>在应用中的所有接口服务都暴露完成后会暴露元数据接口服务</li>
<li>将当前应用实例写入到注册中心</li>
<li>消费端启动创建服务引用时会先去配置中心获取引用接口对应的应用名称</li>
<li>通过应用名称去注册中心获取该应用名称下的实例列表</li>
<li>随机取出服务列表中的一个实例，取出实例中包含的元数据服务信息向元数据服务接口发起调用最终获取到暴露的服务接口URL</li>
</ol>
<link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script> 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://luobiao320.gitee.io/dubbo/dubbo%E6%9C%8D%E5%8A%A1%E8%87%AA%E7%9C%81/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/dubbo/" rel="tag">dubbo</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E6%9C%8D%E5%8A%A1%E8%87%AA%E7%9C%81/" rel="tag">服务自省</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/dubbo/springboot%E4%B8%ADdubbo%E6%9C%8D%E5%8A%A1%E5%AF%BC%E5%87%BA-1/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            springboot中dubbo服务导出-1
          
        </div>
      </a>
    
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "kHx6wceVXdEyiCrLm0sLAwoF-gzGzoHsz",
    app_key: "ArEdGk6wzHOhrXLiwmClhDNH",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2020-2021
        <i class="ri-heart-fill heart_icon"></i> luobiao
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        Powered by <a href="https://hexo.io" target="_blank">Hexo</a>
        <span class="division">|</span>
        Theme - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      
        <li>
          <a href="http://beian.miit.gov.cn/" target="_black" rel="nofollow">赣ICP备2020014719号-1</a>
        </li>
        
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="http://img.luobiao.vip/53119d0bda706bbe350333137a67ea3e_1.jpg" alt="一曲秋殇"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/dubbo">dubbo</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/spring">spring</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/mysql">mysql</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/img/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/img/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->

<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>